package ldh.serialize.util;

import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import ldh.serialize.SerializedConfig;
import ldh.serialize.SerializedItem;
import ldh.serialize.io.ByteBuf;
import ldh.serialize.io.ByteBufFactory;

public class SerializedUtil {

	public static Method getMethod(Class<?> clazz, String methodName, Class<?>... paraClazzs) {
		try {
			return clazz.getMethod(methodName, paraClazzs);
		} catch (Exception e) {
			if (clazz.getSuperclass() != null) {
				return getMethod(clazz.getSuperclass(), methodName,  paraClazzs);
			}
			throw new RuntimeException(clazz + " has not method:" + methodName);
		} 
	}
	
	public static Field getField(Class<?> clazz, String fieldName) {
		try {
			return clazz.getDeclaredField(fieldName);
		} catch (Exception e) {
			if (clazz.getSuperclass() != null) {
				return getField(clazz.getSuperclass(), fieldName);
			}
			throw new RuntimeException(clazz + " has not field:" + fieldName);
		}
	}
	
	public static void setValue(Object obj, Method method, Object params) {
		try {
			method.setAccessible(true);
			method.invoke(obj, params);
		} catch (Exception e) {
			throw new RuntimeException("invoke error!", e);
		} 
	}
	
	@SuppressWarnings("unchecked")
	public static <T>T getValue(Object obj, Method method, Object params) {
		try {
			method.setAccessible(true);
			if (params == null) {
				return (T) method.invoke(obj);
			} else {
				return (T) method.invoke(obj, params);
			}
			
		} catch (Exception e) {
			throw new RuntimeException("invoke error!", e);
		} 
	}
	
	public static void setValue(Object obj, Field field, Object value) {
		try {
			field.setAccessible(true);
			field.set(obj, value);
		} catch (Exception e) {
			throw new RuntimeException("invoke error!");
		} 
	}
	
	public static <T>T getValue(Object obj, Field field) {
		try {
			field.setAccessible(true);
			return (T) field.get(obj);
		} catch (Exception e) {
			throw new RuntimeException("invoke error!");
		} 
	}
	
	public static <O> O newInstance(Class<O> clazz) {
		try {
			return clazz.newInstance();
		} catch (Exception e) {
			throw new RuntimeException("not new newInstance: " + clazz);
		}
	}
	
	public static boolean isSerializable(Class<?> clazz) {
		if (clazz == Serializable.class) {
			return true;
		} else {
			Class<?>[] clazzs = clazz.getInterfaces();
			if (clazzs != null) {
				for (Class<?> c : clazzs) {
					boolean isSerializable = isSerializable(c);
					if (isSerializable) return true;
				}
				Class<?> supper = clazz.getSuperclass();
				return isSerializable(supper);
			}
		}
		
		return false;
	}
	
	public static Set<Method> getAllMethods(Class<?> clazz) {
		Set<Method> methods = new HashSet<Method>();
		Method[] ms = clazz.getDeclaredMethods();
		if (ms != null) {
			for (Method m : ms) {
				methods.add(m);
			}
		}
		Class<?> superClazz = clazz.getSuperclass();
		if (superClazz != null) {
			methods.addAll(getAllMethods(superClazz));
		}
		return methods;
	}
	
	public static Set<Field> getAllFields(Class<?> clazz) {
		Set<Field> fields = new HashSet<Field>();
		Field[] fs = clazz.getDeclaredFields();
		if (fs != null) {
			for (Field f : fs) {
				fields.add(f);
			}
		}
		Class<?> superClazz = clazz.getSuperclass();
		if (superClazz != null) {
			fields.addAll(getAllFields(superClazz));
		}
		return fields;
	}
	
	public static void writeString(ByteBuf out, String str) throws IOException {
		if (str == null) {
			writeByte(out, (byte)0);
			return;
		}
		writeByte(out, (byte)1);
		byte[] tt = str.getBytes("utf-8");
		out.writeBytes(TransferUtil.intToBytes(tt.length));
		out.writeBytes(tt);
	}
	
	public static void writeCompressString(ByteBuf out, String str) throws IOException {
		if (str == null) {
			writeByte(out, (byte)0);
			return;
		}
		writeByte(out, (byte)1);
		byte[] tt = str.getBytes("utf-8");
		out.writeBytes(TransferUtil.varIntToBytes(tt.length));
		out.writeBytes(tt);
	}
	
	public static String readString(ByteBuf in) throws IOException {
		Integer size = readInteger(in);
		if (size != null) {
			byte[] bbs = new byte[size];
			in.readBytes(bbs);
			return new String(bbs, "utf-8");
		}
		return null;
	}
	
	public static String readCompressString(ByteBuf in) throws IOException {
		Integer size = readVarInteger(in);
		if (size != null) {
			byte[] bbs = new byte[size];
			in.readBytes(bbs);
			return new String(bbs, "utf-8");
		}
		return null;
	}
	
	public static String readString(byte[] bytes, Position idx) throws IOException {
		int ll = TransferUtil.bytesToInt(bytes, idx.position);
		idx.increame(4);
		byte[] bbs = new byte[ll];
		System.arraycopy(bytes, idx.position, bbs, 0, bbs.length);
		idx.increame(ll);
		return new String(bbs, "utf-8");
	}
	
	public static String readString(ByteBuffer buffer) throws IOException {
		int ll = buffer.getInt();
		byte[] bbs = new byte[ll];
		buffer.get(bbs);
		return new String(bbs, "utf-8");
	}
	
	public static Integer readVarInteger(ByteBuf in) throws IOException {
		byte isNull = readByte(in);
		if (isNull > 0) {
			return CompressionUtil.byteToVarInt(in);
		}
		return null;
	}
	
	public static Integer readInteger(ByteBuf in) throws IOException {
		byte isNull = readByte(in);
		if (isNull > 0) {
			byte[] lg = new byte[4];
			in.readBytes(lg);
			int ll = TransferUtil.bytesToInt(lg, 0);
			return ll;
		}
		return null;
	}
	
	public static Integer readInteger(byte[] bytes, Position idx) throws IOException {
		int ll = TransferUtil.bytesToInt(bytes, idx.position);
		idx.increame(4);
		return ll;
	}
	
	public static void writeInteger(ByteBuf out, Integer i) throws IOException {
		if (i == null) {
			writeByte(out, (byte)0);
			return;
		}
		writeByte(out, (byte)1);
		out.writeBytes(TransferUtil.intToBytes(i));
	}
	
	public static void writeLong(ByteBuf out, Long i) {
		if (i == null) {
			writeByte(out, (byte)0);
			return;
		}
		writeByte(out, (byte)1);
		out.writeBytes(TransferUtil.longToBytes(i));
	}
	
	public static Long readLong(ByteBuf in) {
		byte isNull = readByte(in);
		if (isNull < 1) return null;
		byte[] lg = new byte[8];
		in.readBytes(lg);
		long ll = TransferUtil.bytesToLong(lg, 0);
		return ll;
	}
	
	public static Long readLong(byte[] bytes, Position p) throws IOException {
		long ll = TransferUtil.bytesToLong(bytes, p.position);
		p.increame(8);
		return ll;
	}
	
	public static void writeBytes(ByteBuf out, byte[] bytes) throws IOException {
		if (bytes == null) {
			writeByte(out, (byte)0);
			return;
		}
		writeByte(out, (byte)1);
		out.writeBytes(TransferUtil.intToBytes(bytes.length));
		out.writeBytes(bytes);
	}
	
	public static byte[] readBytes(ByteBuf in) throws IOException {
		byte isNull = readByte(in);
		if (isNull < 1) {
			int size = readInteger(in);
			byte[] bb = new byte[size];
			in.readBytes(bb);
			return bb;
		}
		return null;
	}
	
	public static void writeByte(ByteBuf out, byte b) {
		out.writeByte(b);
	}
	
	public static byte readByte(ByteBuf in) {
		return in.readByte();
	}
	
	public static void writeKey(StringWriter writer, String key) {
		StringBuilder sb = new StringBuilder();
		sb.append("\"").append(key).append("\"").append(":");
		writer.write(sb.toString());
	}
	
	public static String readKey(StringReader reader) {
		return null;
	}
	
	public static class Position {
		public int position = 0;
		
		public void increame(int t) {
			this.position += t;
		}
	}
	
	public static <T>T toObject(List<SerializedItem> items, T t, byte[] bytes) {
		ByteBuf byteBuf = ByteBufFactory.wrappedBuffer(bytes);
		for (SerializedItem data : items) {
			try {
				Object r = data.read(t, byteBuf);
				if (t == null) return (T) r;
//				System.out.println(data.getValue(t));
			} catch (Exception e) {
				throw new RuntimeException("serialized error!", e);
			}
		}
		return t;
	}
	
	public static <T>T toObject(T t, byte[] bytes) {
		ByteBuf byteBuf = ByteBufFactory.wrappedBuffer(bytes);
		List<SerializedItem> items = SerializedConfig.getSerializeItems(t.getClass());
		for (SerializedItem data : items) {
			try {
				data.read(t, byteBuf);
			} catch (Exception e) {
				throw new RuntimeException("serialized error!", e);
			}
		}
		return t;
	}
	
	public static <T>T toObject(ByteBuf byteBuf, Class<T> clazz) {
		List<SerializedItem> items = SerializedConfig.getSerializeItems(clazz);
		T t = ClassInfo.newClazz(clazz);
		for (SerializedItem data : items) {
			try {
				Object r = data.read(t, byteBuf);
				if (t == null) return (T) r;
			} catch (Exception e) {
				throw new RuntimeException("serialized error!", e);
			}
		}
		return t;
	}
	
	public static byte[] toBytes(List<SerializedItem> items, Object t) {
		ByteBuf byteBuf = ByteBufFactory.buffer(200);
		try {
			for (SerializedItem data : items) {
				data.write(byteBuf, t);
//				byte[] b = byteBuf.array();
//				System.out.println(data.getValue(t));
//				System.out.println(Arrays.toString(b));
			}
			byte[] b = byteBuf.array();
			return b;
		} catch (Exception e) {
			throw new RuntimeException("serialized error!", e);
		}
	}
	
	public static byte[] toBytes(Object t) {
		ByteBuf byteBuf = ByteBufFactory.buffer(400);
		List<SerializedItem> items = SerializedConfig.getSerializeItems(t.getClass());
		try {
			for (SerializedItem data : items) {
				data.write(byteBuf, t);
//				byte[] b = byteBuf.array();
//				System.out.println(data.getValue(t));
//				System.out.println(Arrays.toString(b));
			}
			byte[] b = byteBuf.array();
			return b;
		} catch (Exception e) {
			throw new RuntimeException("serialized error!", e);
		}
	}
	
	public static byte[] toBytes(ByteBuf byteBuf, Object t) {
		List<SerializedItem> items = SerializedConfig.getSerializeItems(t.getClass());
		try {
			for (SerializedItem data : items) {
				data.write(byteBuf, t);
//				byte[] b = byteBuf.array();
//				System.out.println(data.getValue(t));
//				System.out.println(Arrays.toString(b));
			}
			byte[] b = byteBuf.array();
			return b;
		} catch (Exception e) {
			throw new RuntimeException("serialized error!", e);
		}
	}
}
